
import json
import jsonpath

from unit_tools.handle_data.yaml_handler import read_yaml, write_yaml
import re
from unit_tools.debugtalk import DebugTalk
from unit_tools.handle_data.configParse import ConfigParse
from unit_tools.sendrequests import SendRequests
from unit_tools.assertion_utils import Assertions
import allure
from unit_tools.log_utils.recordlog import logs


class RequestsBase:
    def __init__(self):
        self.conf = ConfigParse()
        self.send_request = SendRequests()
        self.asserts = Assertions()

    def parse_and_replace_variable(self, yml_data):
        """
        解析并替换YAML数据中的变量引用：如 ${get_extract_data('headers','Authorization')}
        :param yml_data:解析的YAML数据
        :return:返回的是字典类型dict
        """
        yml_data_str = yml_data if isinstance(yml_data, str) else json.dumps(yml_data, ensure_ascii=False)
        # print(f'解析前{yml_data_str}')
        # 通过${开头，}结尾的字符串，找到yaml用例中的动态函数
        for _ in range(yml_data_str.count('${')):
            if '${' in yml_data_str and '}' in yml_data_str:
                start_index = yml_data_str.index('$')
                end_index = yml_data_str.index('}', start_index)
                variable_data = yml_data_str[start_index:end_index + 1]
                # print(variable_data)
                # 使用正则表达式提取函数名称和参数
                match = re.match(r'\$\{(\w+)\((.*?)\)\}', variable_data)  # 匹配是否${函数名(参数)}格式的数据，返回一个bool值
                # 判断一下是否匹配成功
                if match:
                    func_name, func_params = match.groups()
                    func_params = func_params.split(',') if func_params else []

                    # 使用面向对象类的反射getattr(),找到DebugTalk()类中是否有对应的函数，并调用
                    extract_data = getattr(DebugTalk(), func_name)(*func_params)
                    # print(f'提取到的结果{extract_data}')
                    # 使用正则表达式替换原始字符串中的变量引用为调用后的节点
                    yml_data_str = re.sub(re.escape(variable_data), str(extract_data), yml_data_str)

        # 还原数据，将其转换为字典类型
        try:
            data = json.loads(yml_data_str)
        except json.JSONDecodeError:
            data = yml_data_str
        return data

    @classmethod
    def allure_attach_dict_result(cls, result):
        """
        处理结果是字典类型，就将其转成字符串类型，并做格式化;否则直接返回
        :param result:
        :return:
        """
        if isinstance(result, dict):
            allure_response = json.dumps(result, ensure_ascii=False, indent=4)
        else:
            allure_response = result
        return allure_response

    def execute_test_cases(self, base_info, testcase):
        """
        规范yaml接口信息，执行接口、提取结果以及断言操作
        :param base_info:yaml里面的接口基本信息(dict)
        :param testcase:yaml里面的接口测试用例信息(dict)
        :return:
        """
        try:
            # 处理baseInfo里面的数据
            conf_host = self.conf.get_host('host')
            url = conf_host + base_info['url']
            api_name = base_info['api_name']
            method = base_info['method']
            headers = base_info.get('headers', None)
            if headers is not None:
                headers = self.parse_and_replace_variable(
                    headers)  # if isinstance(headers, str) else headers 如果是字符串则直接返回，不是的话则返回解析后的headers，我们这个项目直接解析headers返回
            cookies = base_info.get('cookie', None)
            if cookies is not None:
                cookies = self.parse_and_replace_variable(cookies)

            case_name = testcase.pop('caseName')  # 获取caseName，删除caseName后再将testcase给返回回去
            # 处理断言结果
            val_result = self.parse_and_replace_variable(testcase.get('validation'))
            testcase['validation'] = val_result
            validation = testcase.pop('validation')
            # 处理接口返回值提取部分，比如token，list数据
            extract = testcase.pop('extract', None)
            extract_list = testcase.pop('extract_list', None)

            param_type = None
            request_params = None
            # 处理参数类型和请求参数
            for param_type, param_value in testcase.items():
                if param_type in ['params', 'data', 'json']:
                    request_params = self.parse_and_replace_variable(param_value)
                    testcase[param_type] = request_params
            # 处理文件上传
            files = testcase.pop('files', None)
            if files:
                for fk, fv in files.items():
                    files = {fk: open(fv, mode='rb')}

            # 发起请求
            response = self.send_request.excute_api_request(api_name=api_name, url=url, method=method,
                                                            headers=headers, case_name=case_name, cookies=cookies,
                                                            files=files, **testcase)
            response_json = response.json()
            response_text = response.text
            status_code = response.status_code
            logs.info(f'接口实际响应结果：{response_text}')
            # 在allure报告test body显示内容
            allure_info = {
                '接口地址': url,
                '接口名称': api_name,
                '请求方式': method,
                '请求头': self.allure_attach_dict_result(headers if headers else "无请求头"),
                'Cookies': self.allure_attach_dict_result(cookies if cookies else "无cookies"),
                '测试用例名称': case_name,
                '参数类型': param_type if param_type else "无需入参",
                '请求参数': self.allure_attach_dict_result(request_params if request_params else "无需入参"),
                '接口实际响应信息': self.allure_attach_dict_result(response_json)
            }
            for title, content in allure_info.items():
                allure.attach(content, title, attachment_type=allure.attachment_type.JSON)

            # 调用处理接口返回值提取的方法
            if extract is not None:
                self.extract_data(extract, response_text)
            if extract_list is not None:
                self.extract_data_list(extract_list, response_text)

            # 处理接口断言
            self.asserts.assert_result(validation, response_json, status_code)

        except Exception as e:
            logs.error(f'出现未知异常，-- {e}')
            raise e

    @classmethod
    def extract_data(cls, testcase_extract, response_text):
        """
        提取单个参数，提取接口返回的参数，支持json提取和正则表达式提取
        :param testcase_extract:字典类型，yaml文件中的extract值，例如:{'token':'$.data'}
        :param response_text:字符串类型，接口的实际返回值
        :return:
        """
        # print(testcase_extract)
        extract_data = None
        try:
            for key, value in testcase_extract.items():
                if any(pat in value for pat in [r'(.*?)', r'(.+?)', r'(\d+)', r'(\d*)']):
                    ext_list = re.search(value, response_text)  # 如果接口yaml文件中含有以上四种类型的正则，则将接口响应数据提取出来
                    extract_data = {key: int(ext_list.group(1)) if r'(\d+)' in value else ext_list.group(1)}
                    logs.info(f'提取到的结果{extract_data}')
                elif "$" in value:
                    extract_json = jsonpath.jsonpath(json.loads(response_text), value)[0]  # 判断yaml是否含有$,先把响应体字符串转化为字典类型
                    extract_data = {key: extract_json} if extract_json else {
                        key: "未提取到数据，请检查接口返回信息或表达式！"}
                    logs.info(f'提取到的结果{extract_data}')
                if extract_data:
                    write_yaml(extract_data)
        except re.error:
            logs.error('正则表达式解析错误，请检查接口yaml文件的extract表达式是否正确')
        except json.JSONDecodeError:
            logs.error('json解析错误，，请检查接口yaml文件的extract表达式是否正确')

    @classmethod
    def extract_data_list(cls, testcase_extract_list, response_text):
        """
        提取多个参数，提取接口返回的参数，支持json提取和正则表达式提取
        :param testcase_extract_list:字典类型，yaml文件中的extract_list值，例如:{'variablesnames':'$.data.list.variablesname'}
        :param response_text:字符串类型，接口的实际返回值
        :return:
        """
        extract_data = None
        try:
            for key, value in testcase_extract_list.items():
                if any(pat in value for pat in [r'(.*?)', r'(.+?)', r'(\d+)', r'(\d*)']):
                    ext_list = re.findall(value, response_text, re.S)  # 如果接口yaml文件中含有以上四种类型的正则，则将接口响应数据提取出来
                    if ext_list:
                        extract_data = {key: ext_list}

                elif "$" in value:
                    extract_json = jsonpath.jsonpath(json.loads(response_text), value)
                    if extract_json:
                        extract_data = {key: extract_json}
                    else:
                        extract_data = {key: "未提取到数据，请检查接口返回信息或表达式！"}

                if extract_data:
                    write_yaml(extract_data)
        except re.error:
            logs.error('正则表达式解析错误，请检查接口yaml文件的extract表达式是否正确')
        except json.JSONDecodeError:
            logs.error('json解析错误，，请检查接口yaml文件的extract表达式是否正确')


if __name__ == '__main__':
    api_info = read_yaml('.././data/login.yaml')[0]
    req = RequestsBase()
    #     #res=req.parse_and_replace_variable(data)
    #     #print(f'解析后：{res}')
    req.execute_test_cases(api_info)
